package com.example.system.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyuncs.exceptions.ClientException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.common.enums.CodeEnum;
import com.example.common.exception.ServiceException;
import com.example.common.utils.R;
import com.example.system.entity.Files;
import com.example.system.entity.User;
import com.example.system.mapper.FileMapper;
import com.example.system.mapper.UserMapper;
import com.example.system.service.IFileService;
import com.example.system.service.IUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.util.List;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 王鹤蒲
 * @since 2023-08-08
 */
@Service
public class FileServiceImpl extends ServiceImpl<FileMapper, Files> implements IFileService {

    @Value("${files.upload.path}")
    private String fileUploadPath;

    @Value("${files.upload.url}")
    private String uploadUrl;


    @Value("${aliyun.oss.endpoint}")
    private String endpoint;

    @Value("${aliyun.oss.bucketName}")
    private String bucketName;

    private Logger logger = LoggerFactory.getLogger(FileServiceImpl.class);

    @Autowired
    private FileMapper fileMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private IUserService iUserService;

    @Override
    public R upload(MultipartFile file) throws IOException {
        String originalFileName = file.getOriginalFilename();
        String type = FileUtil.extName(originalFileName);
        Long size = file.getSize() / 1024;
        String uuid = IdUtil.fastSimpleUUID();
        String fileUUID = uuid + StrUtil.DOT + type;
        File uploadFile = new File(fileUploadPath + fileUUID);
        File parentFile = uploadFile.getParentFile();
        if (!parentFile.exists()) {
            parentFile.mkdirs();
        }
        String url;
        InputStream inputStream = file.getInputStream();
        String md5 = SecureUtil.md5(inputStream);
        inputStream.close();
        Files files = getFilesByMd5(md5);
        if (files != null) {
            url = files.getUrl();
        } else {
            file.transferTo(uploadFile);
            url = uploadUrl + fileUUID;
        }
        Files saveFile = new Files();
        LocalDateTime createTime = LocalDateTime.now();
        saveFile.setName(originalFileName).setType(type).setSize(size).setUrl(url).setMd5(md5).setCreateTime(createTime).setCreateBy(iUserService.getCurrentLoginUser().getUserName());
        System.out.println(iUserService.getCurrentLoginUser());
        System.out.println(saveFile);
        fileMapper.insert(saveFile);
        return R.success(url);
    }


    @Override
    public R uploadOSS(MultipartFile file) {
        String originalFileName = file.getOriginalFilename();
        String type = FileUtil.extName(originalFileName);
        Long size = file.getSize() / 1024;
        String uuid = IdUtil.fastSimpleUUID();
        String fileUUID = uuid + StrUtil.DOT + type;
        String url = null;
        OSS ossClient = null;
        try {
            BufferedInputStream inputStream = new BufferedInputStream(file.getInputStream());
            inputStream.mark(100 * 1024 * 1024);
            String md5 = SecureUtil.md5(inputStream);
            inputStream.reset();
            Files files = getFilesByMd5(md5);
            if (files != null) {
                url = files.getUrl();
            } else {
                EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
                ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
                PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileUUID, inputStream);
                ossClient.putObject(putObjectRequest);
                inputStream.close();
                url =  uploadUrl+fileUUID;
            }
            Files saveFile = new Files();
            LocalDateTime createTime = LocalDateTime.now();
            saveFile.setName(originalFileName).setType(type).setSize(size).setUrl(url).setMd5(md5).setCreateTime(createTime).setCreateBy(iUserService.getCurrentLoginUser().getUserName());
            fileMapper.insert(saveFile);
        } catch (IOException e) {
            log.error(e.getMessage());
            throw new ServiceException(CodeEnum.File_IMPORT_ERROR.getCode(), CodeEnum.File_IMPORT_ERROR.getMsg());
        } catch (OSSException oe) {
            log.error("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            log.error("Error Message:" + oe.getErrorMessage());
            log.error("Error Code:" + oe.getErrorCode());
            log.error("Request ID:" + oe.getRequestId());
            log.error("Host ID:" + oe.getHostId());
            throw new ServiceException(CodeEnum.FILE_UPLOAD_ERROR.getCode(), CodeEnum.FILE_UPLOAD_ERROR.getMsg());
        } catch (ClientException ce) {
            log.error("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            log.error("Error Message:" + ce.getMessage());
            throw new ServiceException(CodeEnum.FILE_UPLOAD_ERROR.getCode(), CodeEnum.FILE_UPLOAD_ERROR.getMsg());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return R.success(url);
    }

    @Override
    public R uploadBatch(MultipartFile[] files) throws IOException {
        for (MultipartFile file : files) {
            return upload(file);
        }
        return R.success();
    }


    public Files getFilesByMd5(String md5) {
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("md5", md5);
        queryWrapper.eq("del_flag", '0');
        List<Files> filesList = fileMapper.selectList(queryWrapper);
        return filesList.isEmpty() ? null : filesList.get(0);
    }

    @Override
    public void download(String fileUUID, HttpServletResponse response) throws IOException {
        File file = new File(fileUploadPath + fileUUID);
        OutputStream outputStream = response.getOutputStream();
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileUUID, "UTF-8"));
        response.addHeader("Access-Control-Expose-Headers", "Content-disposition");
        response.setContentType("application/octet-stream");
        outputStream.write(FileUtil.readBytes(file));
        outputStream.flush();
        outputStream.close();
    }

    public void downloadByOSS(String fileUUID, HttpServletResponse response) {
        OSS ossClient = null;
        try {
            OutputStream outputStream = response.getOutputStream();
            response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileUUID, "UTF-8"));
            response.addHeader("Access-Control-Expose-Headers", "Content-disposition");
            response.setContentType("application/octet-stream");
            String objectName = fileUUID;
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
            OSSObject ossObject = ossClient.getObject(bucketName, objectName);
            InputStream inputStream = ossObject.getObjectContent();
            IoUtil.copy(inputStream, outputStream);
            outputStream.flush();
            inputStream.close();
            outputStream.close();
        } catch (IOException e) {
            log.error(e.getMessage());
            throw new ServiceException(CodeEnum.FILE_DOWNLOAD_ERROR.getCode(), CodeEnum.FILE_DOWNLOAD_ERROR.getMsg());
        } catch (ClientException e) {
            log.error(e.getErrMsg());
            throw new ServiceException(CodeEnum.FILE_DOWNLOAD_ERROR.getCode(), CodeEnum.FILE_DOWNLOAD_ERROR.getMsg());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    private void deleteByOSS(String objectName) {
        OSS ossClient = null;
        try {
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
            ossClient.deleteObject(bucketName, objectName);
        } catch (ClientException e) {
            log.error("Error Message:" + e.getMessage());
            throw new RuntimeException(e);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    @Override
    public R deleteById(Integer id) {
        LocalDateTime updateTime = LocalDateTime.now();
        LambdaQueryWrapper<Files> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Files::getId, id);
        Files file = null;
        file = fileMapper.selectOne(queryWrapper);
        queryWrapper.clear();
        queryWrapper.eq(Files::getMd5, file.getMd5());
        queryWrapper.eq(Files::getDelFlag,"0");
        if (fileMapper.selectCount(queryWrapper) == 1) {
//            File uploadFile = new File(fileUploadPath + file.getUrl().substring(file.getUrl().lastIndexOf("/") + 1));
//            uploadFile.delete();
            deleteByOSS(file.getUrl().substring(file.getUrl().lastIndexOf("/") + 1));
            if (file.getUrl().equals(iUserService.getCurrentLoginUser().getAvatar())) {
                LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
                lambdaUpdateWrapper.eq(User::getUserName, iUserService.getCurrentLoginUser().getUserName());
                lambdaUpdateWrapper.set(User::getAvatar, "");
                userMapper.update(new User(), lambdaUpdateWrapper);
            }
        }
        file = new Files().setId(id).setDelFlag("1").setEnable(false).setUpdateTime(updateTime);
        if (iUserService.getCurrentLoginUser() != null) {
            file.setUpdateBy(iUserService.getCurrentLoginUser().getUserName());
        }
        fileMapper.updateById(file);
        return R.success();
    }

    @Override
    public R deleteBatchByIds(Integer[] ids) {
        String currentUserName = null;
        if (iUserService.getCurrentLoginUser() != null) {
            currentUserName = iUserService.getCurrentLoginUser().getUserName();
        }
        for (Integer id : ids) {
            LocalDateTime updateTime = LocalDateTime.now();
            Files file = new Files().setId(id).setDelFlag("1").setUpdateTime(updateTime);
            file.setUpdateBy(currentUserName);
            fileMapper.updateById(file);
        }
        return R.success();
    }

    @Override
    public R selectAll(int currentPage, int pageSize, String name) {
        Page<Files> page = new Page<>(currentPage, pageSize);
        LambdaQueryWrapper<Files> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Files::getDelFlag, '0');
        lambdaQueryWrapper.like(StrUtil.isNotBlank(name), Files::getName, name);
        fileMapper.selectPage(page, lambdaQueryWrapper);
        return R.success(page);
    }

    @Override
    public R update(Files file) {
        file.setUpdateBy(iUserService.getCurrentLoginUser().getUserName());
        if (!file.getEnable() && file.getUrl().equals(iUserService.getCurrentLoginUser().getAvatar())) {
            LambdaQueryWrapper<Files> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Files::getUrl, file.getUrl());
            queryWrapper.eq(Files::getDelFlag,"0");
            if (fileMapper.selectCount(queryWrapper) == 1) {
                LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
                lambdaUpdateWrapper.eq(User::getUserName, iUserService.getCurrentLoginUser().getUserName());
                lambdaUpdateWrapper.set(User::getAvatar, "");
                userMapper.update(new User(), lambdaUpdateWrapper);
            }
        }
        LocalDateTime updateTime = LocalDateTime.now();
        file.setUpdateTime(updateTime);
        fileMapper.updateById(file);
        return R.success();
    }


}
